Skip to content

[auto-rotation feature] feat: add key auto-rotation specification + manual rotation for all key types#968

Open
Manuthor wants to merge 9 commits into
developfrom
docs/key-autorotation-spec
Open

[auto-rotation feature] feat: add key auto-rotation specification + manual rotation for all key types#968
Manuthor wants to merge 9 commits into
developfrom
docs/key-autorotation-spec

Conversation

@Manuthor

@Manuthor Manuthor commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Delivers the complete key auto-rotation specification and the full manual-rotation
implementation for all key types.

This is PR 1 of 4 in the key auto-rotation feature stack:

develop
  ← PR 1  docs/key-autorotation-spec          ← this PR (#968)
  ← PR 2  feat/key-rotation-ckms-ui            (#988)
  ← PR 3  feat/key-rotation-scheduler          (#970)
  ← PR 4  feat/key-rotation-notifications      (#971)

What's included

Documentation

  • Full rotation-policy attribute table (x-rotate-interval, x-rotate-name,
    x-rotate-offset, x-rotate-generation, x-rotate-date)
  • Seven rotation scenarios with Mermaid diagrams: plain key, wrapping key,
    wrapped key, asymmetric key pair, wrapped private key (CoverCrypt),
    certificate renewal (ReCertify), server-wide KEK
  • KMIP link chain after successive rotations (ReplacementObjectLink /
    ReplacedObjectLink)
  • Attribute update table for auto-rotation vs manual rekey (intentional asymmetry)
  • Server-side scheduler configuration reference
  • HSM-resident key limitation note
  • Step-by-step end-to-end configuration guide

Implementation — Re-Key, Re-Key Key Pair, Re-Certify

Complete manual-rotation implementation for all six scenarios:

  1. Plain symmetric key — new material, new UID, rotation metadata
  2. Wrapping key — rotate + re-wrap all dependants (two-phase commit)
  3. Wrapped key — unwrap → new material → re-wrap
  4. Asymmetric key pair — new private key + new public key UIDs, all curve types
    (RSA, EC, ML-KEM, ML-DSA, SLH-DSA, X25519, secp256k1, CoverCrypt)
  5. Wrapped private key / CoverCrypt — CoverCrypt rekeying with wrapping preserved
  6. Server-wide KEK — transparent via default wrapping config

Implements ReCertify (KMIP 2.1 §6.1.45) for certificate renewal:

  • Self-signed and CA-signed renewal
  • Replacement link chain management (ReplacedObjectLink / ReplacementObjectLink)
  • Offset-based PreActive state for future-activation certificates

All 344 test vectors pass.

Breaking changes

None.

Reviewer notes

This document is the canonical reference for all subsequent PRs in this stack.
Please review terminology and attribute semantics carefully; changes here will
cascade to the implementation PRs.

@Manuthor Manuthor changed the title docs: add key auto-rotation specification [auto-rotation feature] docs: add key auto-rotation specification May 28, 2026
@Manuthor Manuthor changed the title [auto-rotation feature] docs: add key auto-rotation specification [auto-rotation feature] feat: add key auto-rotation specification + manual rotation for all key types May 29, 2026
@Manuthor Manuthor force-pushed the docs/key-autorotation-spec branch 2 times, most recently from fa12cc6 to 5f27e21 Compare May 31, 2026 06:30
@Manuthor Manuthor requested review from Copilot and tbrezot and removed request for Copilot June 1, 2026 08:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR is the first in the key auto-rotation feature stack. It introduces the canonical auto-rotation specification document and lands the underlying manual rotation implementation for keys and certificates (ReKey, ReKeyKeyPair, ReCertify), plus the database plumbing and test vectors needed for wrapping-key dependant rewrites and certificate renewal link chains.

Changes:

  • Add comprehensive documentation for rotation policy attributes, scheduler semantics, and rotation/renewal scenarios (with diagrams and attribute tables).
  • Refactor and extend server KMIP operations to implement manual rotation flows for symmetric keys, key pairs, and certificate renewal (ReCertify) using a shared orchestration trait.
  • Add ObjectsStore::find_wrapped_by() across SQL backends to support wrapping-key rotations that must re-wrap dependants, and extend test vectors/runner docs accordingly.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
README.md Adds a documentation link for scheduled key auto-rotation.
documentation/mkdocs.yml Registers the new Key Auto-Rotation documentation page in the nav.
documentation/docs/kmip_support/key_auto_rotation.md Adds the full auto-rotation specification and scenarios (policy attributes, flows, roadmap).
crate/test_kms_server/src/vector_runner.rs Registers new negative and positive vectors for ReCertify and offset state verification.
crate/test_kms_server/README.md Updates vector counts and documents newly added vectors.
crate/server/src/core/wrapping/wrap.rs Tightens self-wrap handling and bypasses KEK ownership checks for server-wide KEK.
crate/server/src/core/operations/rekey/* Introduces a new rekey/ module with shared orchestration + symmetric/keypair flows.
crate/server/src/core/operations/recertify.rs Adds server-side ReCertify implementation and dependant relinking behavior.
crate/server/src/core/operations/{mod.rs,message.rs,dispatch.rs} Wires ReCertify through dispatch and operation processing.
crate/server/src/core/operations/key_ops/mod.rs Fixes lifecycle setup so PreActive objects retain a future activation_date.
crate/server/src/core/operations/certify/* Broadens visibility of certify helpers/types for reuse by ReCertify.
crate/server/src/core/kms/kmip.rs Adds the KMS::recertify entry point.
crate/interfaces/src/stores/objects_store.rs Adds find_wrapped_by() to the store trait (default empty).
crate/server_database/src/core/database_objects.rs Exposes Database::find_wrapped_by() across backends.
crate/server_database/src/stores/sql/{sqlite,pgsql,mysql}.rs Implements find_wrapped_by() using JSON queries in each SQL backend.
crate/kmip/src/kmip_2_1/{kmip_operations,kmip_messages}.rs Adds KMIP 2.1 ReCertify request/response types + message (de)serialization.
crate/kmip/src/kmip_1_4/kmip_operations.rs Adds 1.4↔2.1 conversions for ReCertify types and operation mapping.
CHANGELOG/feat_key-rotation-manual.md Adds the required branch-specific changelog for manual rotation changes.
CHANGELOG/docs_key-autorotation-spec.md Adds the required branch-specific changelog for the spec doc addition.

Comment thread crate/server/src/core/operations/rekey/common.rs Outdated
Comment thread crate/server/src/core/operations/rekey/common.rs Outdated
Comment thread crate/kmip/src/kmip_2_1/kmip_operations.rs
Comment thread crate/server/src/core/operations/recertify.rs Outdated
Comment thread crate/server_database/src/stores/sql/sqlite.rs Outdated
Comment thread crate/server_database/src/stores/sql/sqlite.rs Outdated
Comment thread crate/server_database/src/stores/sql/pgsql.rs Outdated
Comment thread crate/server_database/src/stores/sql/mysql.rs Outdated
Comment thread crate/server_database/src/stores/sql/mysql.rs Outdated
Comment thread crate/server_database/src/stores/sql/mysql.rs Outdated
@Manuthor Manuthor force-pushed the docs/key-autorotation-spec branch 2 times, most recently from 291bfbd to 1b09922 Compare June 1, 2026 19:40

@tbrezot tbrezot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not go through all diffs, but thanks to your documentation I could grasp enough of the idea to have a few questions (cf my comment of the doc). I will complete the review once we have discussed those points.

Comment thread crate/interfaces/src/stores/objects_store.rs
Comment thread crate/interfaces/src/stores/objects_store.rs
Comment thread crate/kmip/src/kmip_1_4/kmip_operations.rs Outdated
Comment thread crate/kmip/src/kmip_1_4/kmip_operations.rs Outdated
Comment thread crate/kmip/src/kmip_2_1/kmip_operations.rs Outdated
Comment thread documentation/docs/kmip_support/key_auto_rotation.md Outdated
Comment thread documentation/docs/kmip_support/key_auto_rotation.md Outdated
Comment thread documentation/docs/kmip_support/key_auto_rotation.md
Comment thread documentation/docs/kmip_support/key_auto_rotation.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great design documentat!

Auto-rotation is an extremely complex topic (as in "it touches everything and thus impacts everything"). After reading this document I am left wondering with a few things:

  • it seems useful to give the set of all rotations of a given key an identity (in Google's terms, this is a key-set, we could also call it key-chain to be more explicit about its structure; I will be using key-set in what follows), since from the user point of view, all the keys generated by auto-rotation are actually the same key. In a first approximation, it seems the user should not have to know more than that, which brings up the key selection issue.

  • It seems needed to be able to:

    • encrypt with a key-set (which uses its latest key)
    • encrypt with a specific key. We have the IDs, but since they are automatically and randomly generated, I don't think they can be of much use. We could instead have a special syntax to describe the version of the key to use in a key-set (e.g. key-set-name@version)
    • decrypt with a key-set (which tries each key in the key-set until it can successful decrypt)
    • decrypt with a specific key (same as encrypt)
    • decrypt with a key-set with a temporal hint (for example if we know the creation date of a ciphertext there is no need to attempt decrypting it with newer keys in the key-set).

    In Covercrypt we internally use similar techniques for key-rotation: each right is associated to a key-set which is implemented as a singly-linked list (why do you need a doubly-linked list in the KMS? It seems we never want to traverse it in chronological order but always in anti-chronological order when attempting to decrypt a document). We therefore have a key-set ID (the right), and each key in the set can be selected by its distance to the latest (the depth of the smallest prefix of the chain containing it).

  • it is never made explicit here but it seems natural to prevent rotating keys that are not activated or expired.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems useful to give the set of all rotations a key-set identity.

Agree. Our current design uses the x-rotate-name vendor attribute as the key-set identity. All rotations of a key share the same x-rotate-name value, which serves as the stable identifier the user interacts with.

Let me paraphrase just to be sure I've understood:

  • Encrypt with key-setLocate by x-rotate-name + x-rotate-latest=true → returns the current active key → Encrypt
  • Encrypt with specific version → Use the key's UID directly (or x-rotate-name + x-rotate-generation=N)
  • Decrypt with key-set → Client-side: try latest first, walk ReplacedObjectLink chain backward (or server could try each key — not yet implemented)
  • Decrypt with temporal hint → Use creation-date metadata to narrow the candidate set

The key-set-name@version syntax is a great suggestion. We could support it as a UID alias in the Locate / operation path.

It seems we never want to traverse it in chronological order but always in anti-chronological order when attempting to decrypt.

Answer: The doubly-linked list (both ReplacedObjectLink and ReplacementObjectLink) is mandated by KMIP 2.1 §6.1.46 (Re-key) and §11.28 (Link Type Enumeration), Table 464:

  • Replacement Object Link (value 00000106) on the old key (points forward to the new key)
  • Replaced Object Link (value 00000107) on the new key (points backward to the old key)

Use cases for forward traversal:

  1. Admin cleanup: "find all successors of this key and revoke/destroy them" (e.g., security incident response)
  2. Audit trail: walk the chain forward to show the full rotation history
  3. Migration: when upgrading a key to a new algorithm, walk forward to find the latest version

In practice, decryption indeed only needs backward traversal (newest → oldest). But the KMIP spec requires both directions, and both are cheap to maintain (one link per key).

It seems natural to prevent rotating keys that are not activated or expired.

Answer: Agreed and partially implemented:

  • ✅ Deactivated keys → error
  • ✅ Destroyed keys → error (can't even retrieve)
  • ⚠️ PreActive keys → currently allowed (debatable — a PreActive key hasn't been used yet, so rotation might be premature)
  • ⚠️ Compromised keys → currently allowed (might want to prevent rotation of compromised keys to avoid confusion)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your answer. Your rephrase is correct. Thanks for pointing out the KMIP requirements (since otherwise the use-cases you mention seem to work by walking the chain backward from the latest key of the key-set that should always be "locatable" given the owm of a given key in the key-set).

About forbidding rotation of non-active keys, I was talking about auto-rotation. But even then it is unclear what is the sensible thing to do and would require more discussion imho.

Related to that is the interaction of rotation with the key-set view of the system: what happens if the user rotates an older version of a key? Does it fork the chain? Does it truncate it? Imho rotation should always target key-set (or equivalently, manual rotation of keys that are not marked as latest should not be authorized)

@Manuthor Manuthor mentioned this pull request Jun 3, 2026
@Manuthor Manuthor force-pushed the docs/key-autorotation-spec branch 3 times, most recently from 9eb009a to 854aa02 Compare June 8, 2026 10:59
@Manuthor Manuthor force-pushed the docs/key-autorotation-spec branch 2 times, most recently from 5911dc5 to dcc5aa5 Compare June 14, 2026 07:48

@tbrezot tbrezot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of effort has been put into this MR, and it greatly improves the initial implementation. I still have a few comments though:

  • a few nitpicks that are trivial to correct;
  • maybe an issue concerning the key-selection procedure;
  • some deeper concerns about concurrency management, which are orthogonal to this feature and may be addressed in a dedicated MR.

Also, since it is structuring, it might gain from requesting a review from someone else. In any case, I may spend more time on it if you delay the merge (since it is huge, I could not read everything).

Comment thread crate/server/src/config/command_line/clap_config.rs Outdated
Comment thread crate/server/src/config/command_line/clap_config.rs
Comment thread crate/server/src/core/cover_crypt/rekey_keys.rs
Comment thread crate/server/src/core/operations/attributes/modify.rs
Comment thread crate/server/src/core/operations/attributes/set.rs Outdated
Comment thread crate/server/src/core/operations/rekey/common.rs
Comment thread crate/server/src/core/operations/rekey/keypair.rs
Comment thread crate/server/src/core/operations/rekey/keypair.rs
Comment thread crate/server/src/core/operations/rekey/keypair.rs Outdated
Comment thread crate/server/src/core/operations/rekey/keypair.rs Outdated
@Manuthor Manuthor force-pushed the docs/key-autorotation-spec branch 5 times, most recently from d7c575c to f610a78 Compare June 19, 2026 08:40
Manuthor and others added 9 commits June 19, 2026 23:39
…pleteness

- server/src/core/operations/{recertify,revoke}.rs: wrap
  retrieve_object_for_operation and revoke_key_core calls with
  Box::pin to suppress clippy::large_futures.

- routes/crypto/{decrypt,encrypt,sign,unwrap}.rs: same Box::pin
  treatment for all retrieve_object_for_operation call sites.

- interfaces/src/hsm/hsm_store.rs: add missing set_key_dates and
  set_key_label methods to the mockall! test double so it matches
  the updated HSM trait.

- server/src/core/uid_utils.rs: rename second mod tests to
  mod hsm_tests to eliminate the 'name defined multiple times' error.

- server_database/src/stores/sql/sqlite.rs: replace undefined
  SQLITE_QUERIES with PGSQL_QUERIES in the regression-guard test
  (SQLite uses PGSQL_QUERIES / query.sql via get_sqlite_query!).

- nix/expected-hashes/{server.vendor.static,ui.vendor.non-fips}.sha256:
  update hashes after thiserror 2.0.17→2.0.18 Cargo.lock bump.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The WASM non-fips vendor tarball hash changed after dependencies were
updated. Update to sha256-C/9J14QduLLtw4KtWmxOgLAZKUvEhSnsMu5rF5PZS/A=.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add pin_fn_depth macro variant in dispatch for functions returning (Resp, Option<u32>)
- Switch MACVerify dispatch arm to pin_fn_depth to unpack mac_verify tuple return
- Remove unused kms.message() wrapper (route calls operations::message() directly)
- Remove unused RequestMessage/ResponseMessage imports in kms/kmip.rs
- Fix google_cse encrypt/decrypt callers to unpack (Resp, Option<u32>) tuples
- Fix kmip.rs single-op dispatch path (dispatch() returns KResult<Operation>, not tuple)
- Fix ui.vendor.non-fips.sha256 Nix hash

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The method was removed but is called by 6+ test files in crate/server/src/tests/.
Restore it with #[allow(dead_code)] since it is only called from cfg(test) code
and cargo build (without --tests) cannot see those callers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ckms subprocess cannot negotiate ML-DSA-44 TLS handshake without
calling init_openssl_providers() at startup. Mark as ignored until
the ckms binary is updated to support PQC TLS client connections.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Manuthor Manuthor force-pushed the docs/key-autorotation-spec branch from f610a78 to 7b7cbd4 Compare June 19, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants